package meatslope2.gameengine.scene;

import java.util.ArrayList;
import java.util.List;

import javax.microedition.khronos.opengles.GL10;

import meatslope2.constants.BitmapConstants;
import meatslope2.constants.TileConstants;
import meatslope2.gameengine.gameobjects.Tile;
import meatslope2.gameengine.helpers.Utils;
import meatslope2.gameengine.primatives.Vector2;

/**
 * Stores a 2D array for tile collision detection as well as a plain array for drawing tiles
 * 
 * @author Mitchell Thelen
 *
 */
public class TileWorld 
{
	// ---
	// Members
	// ---
	private final int pixelWidth;
	private final int pixelHeight;
	
	private final int tilesAcross;
	private final int tilesUp;
	
	//This is a 2D array that references a tile index for collisions
	private int[][] collisionGrid;
	
	//This is an array of tile positions and indexes for drawing
	private CachedTile[][] drawingGrid;
	
	// ---
	// Getters/Setters
	// ---
	public int getPixelHeight()
	{
		return this.pixelHeight;
	}
	
	public int getPixelWidth()
	{
		return this.pixelWidth;
	}
	
	public int getTilesAcross()
	{
		return this.tilesAcross;
	}
	
	public int getTilesUp()
	{
		return this.tilesUp;
	}
	
	public int getTile(int x, int y)
	{
		if(x < 0 || x >= collisionGrid.length || y < 0 || y >= collisionGrid[x].length)
			return TileConstants.NOT_SET;
		
		return this.collisionGrid[x][y];
	}
	
	private TileWorld(int width, int height)
	{
		if((width % BitmapConstants.TILE_WIDTH) != 0)
			throw new IllegalArgumentException("TileWorld grid width must be evenly divisible by tile width");
		else if((height % BitmapConstants.TILE_HEIGHT) != 0)
			throw new IllegalArgumentException("TileWorld grid height must be evenly divisible by tile height");
		
		this.pixelWidth = width;
		this.pixelHeight = height;
		
		this.tilesAcross = width / BitmapConstants.TILE_WIDTH;
		this.tilesUp = height / BitmapConstants.TILE_HEIGHT;
	}
	
	private void setTiles(int[][] collisionGrid)
	{
		this.collisionGrid = collisionGrid;
		this.drawingGrid = buildDrawingGrid();
	}
	
	/**
	 * Builds a grid (buckets of tiles in the x-direction that are cached and ready to draw)
	 */
	private CachedTile[][] buildDrawingGrid()
	{
		final CachedTile[][] newDrawingGrid = new CachedTile[tilesAcross][];
		for(int x = 0; x < this.tilesAcross; x++)
		{
			List<CachedTile> bucket = new ArrayList<CachedTile>();
			for(int y = 0; y < this.tilesUp; y++)
			{
				int tileIndex = this.collisionGrid[x][y];
				if(tileIndex == TileConstants.NOT_SET)
					continue;
				
				Vector2 pos = new Vector2(x * BitmapConstants.TILE_WIDTH, y * BitmapConstants.TILE_HEIGHT);
				CachedTile newCachedTile = new CachedTile(tileIndex, pos);
				bucket.add(newCachedTile);
			}
			
			CachedTile[] bucketContent = Utils.toArray(bucket, new CachedTile[bucket.size()]);
			newDrawingGrid[x] = bucketContent;
		}
		
		return newDrawingGrid;
		
	}
	
	/**
	 * TODO: we do not need game object position for Tile
	 * 
	 * This class keeps a reference to its static tile and a location that will
	 * need to be drawn relative to a camera
	 * @author Mitchell Thelen
	 *
	 */
	private class CachedTile
	{
		public final int tileIndex;
		public final Vector2 position;
		
		public CachedTile(int tileIndex, Vector2 position)
		{
			this.tileIndex = tileIndex;
			this.position = position;
		}
	}
	
	public static class Factory
	{
		private static int[][] currentGrid;
		private static TileWorld currentWorld;
		
		//TODO: nuke this sample
		public static TileWorld buildLevel1()
		{
			int width = BitmapConstants.TILE_WIDTH * 8;
			int height = BitmapConstants.TILE_HEIGHT * 4;
			
			//Create world
			currentWorld = new TileWorld(width, height);
			
			//Create grid and drawing tiles
			currentGrid = createInitialCollisionGrid(8, 4);
			
			addTile(TileConstants.RAMP_LEFT_1.index(), 0, 0);
			addTile(TileConstants.RAMP_LEFT_2.index(), 1, 0);
			addTile(TileConstants.BLOCK.index(), 2, 0);
			
			//assign grid and return
			currentWorld.setTiles(currentGrid);
			return currentWorld;
		}
		
		private static int[][] createInitialCollisionGrid(int tilesAcross, int tilesUp) 
		{
			final int[][] collisionGrid = new int[tilesAcross][tilesUp];
			
			for(int x = 0; x < tilesAcross; x++)
				for(int y = 0; y < tilesUp; y++)
					collisionGrid[x][y] = TileConstants.NOT_SET;
			
			return collisionGrid;
		}

		private static void addTile(int tileIndex, int posX, int posY)
		{
			currentGrid[posX][posY] = tileIndex;
		}
	}

	//TODO: draw relative to camera
	public void draw(GL10 pGL) 
	{
		for(int x = 0; x < this.drawingGrid.length; x++)
		{
			CachedTile[] bucket = this.drawingGrid[x];
			for(int y = 0; y < bucket.length; y++)
			{
				CachedTile cachedTile = bucket[y];
				Tile tile = TileConstants.getTile(cachedTile.tileIndex);
			
				tile.getPosition().set(cachedTile.position);
				tile.draw(pGL);
			}
		}
	}
}
